﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Asol.Reporting.Support.Data;

namespace Asol.Reporting.Support
{
    #region class FormatStyle : Formátovací třída. Implicitní formátování standardních datových typů.
    /// <summary>
    /// Obecná třída, která implementuje IFormatStyle, a podporuje implicitní převody hodnoty do formátu jak podle masky, tak podle seznamu editačních položek.
    /// Pokud tuto třídu používá konkrétní IDataSource, pak může naplnit masku nebo editační položky, 
    /// a v případě potřeby může overridovat metody ConvertCodeToText*().
    /// </summary>
    public class FormatStyle
    {
        #region Konstrukce a public property
        /// <summary>
        /// Konstruktor základní
        /// </summary>
        public FormatStyle() { }
        /// <summary>
        /// Konstruktor se zadáním editační masky
        /// </summary>
        /// <param name="formatMask"></param>
        public FormatStyle(string formatMask)
        {
            this.FormatMask = formatMask;
        }
        /// <summary>
        /// Konstruktor se zadáním položek editačního stylu
        /// </summary>
        /// <param name="editStyleItems"></param>
        public FormatStyle(IEnumerable<FormatStyleItem> editStyleItems)
        {
            this.EditStyleItems = new List<FormatStyleItem>(editStyleItems);
        }
        /// <summary>
        /// Maska formátu
        /// </summary>
        public string FormatMask { get; set; }
        /// <summary>
        /// Jméno formátovacího předpisu, který řeší datový zdroj
        /// </summary>
        public string FormatName { get; set; }
        /// <summary>Položky editačního stylu</summary>
        public IEnumerable<FormatStyleItem> EditStyleItems { get; set; }
        #endregion
        #region Implicitní metody převodu hodnoty na text
        /// <summary>
        /// Virtuální metoda převodu hodnoty na text, řeší rozdělení podle obsahu maska/položky
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public virtual string ConvertValueToText(ValueData value)
        {
            if (value.IsEmpty) return "";

            // Podle explicitního jména editačního stylu skrze datový zdroj:
            string formatName = this.GetFormatNameForType(value);
            if (!String.IsNullOrEmpty(formatName)) return this.ConvertCodeToTextByFormatName(value, formatName);

            // Podle položek editačního stylu?
            IEnumerable<FormatStyleItem> items = this.GetItemsForType(value);
            if (items != null) return this.ConvertCodeToTextByItems(value, items);

            // Podle masky?
            string mask = this.GetMaskForType(value);
            if (mask != null) return this.ConvertCodeToTextByMask(value, mask);

            return value.ToString();
        }
        #region ... detekce datového typu podle obsahu
        ///// <summary>
        ///// Upraví vstupující hodnotu a detekuje její datový typ. Řeší null, nullable, standardní systémové typy.
        ///// </summary>
        ///// <param name="data"></param>
        ///// <param name="valueType"></param>
        ///// <returns></returns>
        //protected virtual object ConvertDataToType(object data, out ValueDataType valueType)
        //{
        //    if (data == null) { valueType = ValueDataType.Null; return null; }
        //    if (data is Boolean?) return this.ConvertNullableDataToType<Boolean>((Boolean?)data, out valueType);
        //    if (data is Byte?) return this.ConvertNullableDataToType<Byte>((Byte?)data, out valueType);
        //    if (data is SByte?) return this.ConvertNullableDataToType<SByte>((SByte?)data, out valueType);
        //    if (data is Int16?) return this.ConvertNullableDataToType<Int16>((Int16?)data, out valueType);
        //    if (data is Int32?) return this.ConvertNullableDataToType<Int32>((Int32?)data, out valueType);
        //    if (data is Int64?) return this.ConvertNullableDataToType<Int64>((Int64?)data, out valueType);
        //    if (data is UInt16?) return this.ConvertNullableDataToType<UInt16>((UInt16?)data, out valueType);
        //    if (data is UInt32?) return this.ConvertNullableDataToType<UInt32>((UInt32?)data, out valueType);
        //    if (data is UInt64?) return this.ConvertNullableDataToType<UInt64>((UInt64?)data, out valueType);
        //    if (data is Single?) return this.ConvertNullableDataToType<Single>((Single?)data, out valueType);
        //    if (data is Double?) return this.ConvertNullableDataToType<Double>((Double?)data, out valueType);
        //    if (data is Decimal?) return this.ConvertNullableDataToType<Decimal>((Decimal?)data, out valueType);
        //    if (data is Char?) return this.ConvertNullableDataToType<Char>((Char?)data, out valueType);
        //    if (data is DateTime?) return this.ConvertNullableDataToType<DateTime>((DateTime?)data, out valueType);
        //    if (data is TimeSpan?) return this.ConvertNullableDataToType<TimeSpan>((TimeSpan?)data, out valueType);
        //    return ConvertNonNullableDataToType(data, out valueType);
        //}
        ///// <summary>
        ///// Detekuje typ vstupující hodnoty a vrací ji převedenou na základní typy, pokud jsou detekovány (Boolean, Int64, Decimal, Sting, DateTime, TimeSpan).
        ///// </summary>
        ///// <param name="data"></param>
        ///// <param name="valueType"></param>
        ///// <returns></returns>
        //protected virtual object ConvertNonNullableDataToType(object data, out ValueDataType valueType)
        //{
        //    if (data is Boolean) return this.ConvertNonNullableDataToType((Boolean)data, ValueDataType.Boolean, out valueType);
        //    if (data is Byte) return this.ConvertNonNullableDataToType((Int64)data, ValueDataType.Integer, out valueType);
        //    if (data is SByte) return this.ConvertNonNullableDataToType((Int64)data, ValueDataType.Integer, out valueType);
        //    if (data is Int16) return this.ConvertNonNullableDataToType((Int64)data, ValueDataType.Integer, out valueType);
        //    if (data is Int32) return this.ConvertNonNullableDataToType((Int64)data, ValueDataType.Integer, out valueType);
        //    if (data is Int64) return this.ConvertNonNullableDataToType((Int64)data, ValueDataType.Integer, out valueType);
        //    if (data is UInt16) return this.ConvertNonNullableDataToType((Int64)data, ValueDataType.Integer, out valueType);
        //    if (data is UInt32) return this.ConvertNonNullableDataToType((Int64)data, ValueDataType.Integer, out valueType);
        //    if (data is UInt64) return this.ConvertNonNullableDataToType((Int64)data, ValueDataType.Integer, out valueType);
        //    if (data is Single) return this.ConvertNonNullableDataToType((Decimal)data, ValueDataType.Decimal, out valueType);
        //    if (data is Double) return this.ConvertNonNullableDataToType((Decimal)data, ValueDataType.Decimal, out valueType);
        //    if (data is Decimal) return this.ConvertNonNullableDataToType((Decimal)data, ValueDataType.Decimal, out valueType);
        //    if (data is Char) return this.ConvertNonNullableDataToType(((Char)data).ToString(), ValueDataType.Character, out valueType);
        //    if (data is DateTime) return this.ConvertNonNullableDataToType((DateTime)data, ValueDataType.DateTime, out valueType);
        //    if (data is TimeSpan) return this.ConvertNonNullableDataToType((TimeSpan)data, ValueDataType.TimeSpan, out valueType);
        //    valueType = ValueDataType.Other;
        //    return data;
        //}
        ///// <summary>
        ///// Detekuje obsah nullable datových typů: detekuje NULL a řeší jej, a pokud není NULL pak řeší reálný obsah hodnoty.
        ///// </summary>
        ///// <typeparam name="T"></typeparam>
        ///// <param name="nullable"></param>
        ///// <param name="valueType"></param>
        ///// <returns></returns>
        //protected virtual object ConvertNullableDataToType<T>(T? nullable, out ValueDataType valueType) where T : struct
        //{
        //    if (!nullable.HasValue)
        //    {
        //        valueType = ValueDataType.Null;
        //        return null;
        //    }
        //    return ConvertNonNullableDataToType(nullable.Value, out valueType);
        //}
        ///// <summary>
        ///// Víceméně formální metoda. Dovoluje přetypovat data a nastavit out ValueDataType v jednom příkazovém řádku.
        ///// </summary>
        ///// <param name="data"></param>
        ///// <param name="currentValueType"></param>
        ///// <param name="valueType"></param>
        ///// <returns></returns>
        //protected virtual object ConvertNonNullableDataToType(object data, ValueDataType currentValueType, out ValueDataType valueType)
        //{
        //    valueType = currentValueType;
        //    return data;
        //}
        #endregion
        #region ... formátování podle názvu formátu ve spolupráci s datovým zdrojem
        /// <summary>
        /// Vrátí název editačního stylu pro danou hodnotu
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual string GetFormatNameForType(ValueData value)
        {
            return this.FormatName;
        }
        /// <summary>
        /// Využije datový zdroj pro formátování hodnoty podle jména formátu
        /// </summary>
        /// <param name="value"></param>
        /// <param name="formatName"></param>
        /// <returns></returns>
        protected virtual string ConvertCodeToTextByFormatName(ValueData value, string formatName)
        {
            return Assistant.DataSource.FormatValueToText(value, formatName);
        }
        #endregion
        #region ... formátování podle položek
        /// <summary>
        /// Vrátí položky editačního stylu pro daný datový typ.
        /// Implicitně vrátí this.EditStyleItems.
        /// Může vrátit null, pak se použije možnost formátování podle masky.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual IEnumerable<FormatStyleItem> GetItemsForType(ValueData value)
        {
            return this.EditStyleItems;
        }
        /// <summary>
        /// Virtuální metoda převodu hodnoty na text, konkrétně řeší formátování podle soupisu položek
        /// </summary>
        /// <param name="value">Hodnota</param>
        /// <param name="items">Položky</param>
        /// <returns></returns>
        protected virtual string ConvertCodeToTextByItems(ValueData value, IEnumerable<FormatStyleItem> items)
        {
            switch (value.Type)
            {
                case ValueDataType.Integer:
                    return this.ConvertInt64CodeToTextByItems(value.ValueInteger, items);
                case ValueDataType.Decimal:
                    return this.ConvertDecimalCodeToTextByItems(value.ValueDecimal, items);
                case ValueDataType.Character:
                    return this.ConvertStringCodeToTextByItems(value.ValueCharacter, items);
            }
            return this.ConvertStringCodeToTextByItems(value.ToString(), items);
        }
        /// <summary>
        /// Virtuální metoda převodu hodnoty na text, konkrétně řeší formátování podle soupisu položek, 
        /// když hodnota je celočíselného typu.
        /// </summary>
        /// <param name="value">Hodnota</param>
        /// <param name="items">Položky</param>
        /// <returns></returns>
        private string ConvertInt64CodeToTextByItems(long value, IEnumerable<FormatStyleItem> items)
        {
            foreach (FormatStyleItem item in items)
            {
                long code = Convert.ToInt64(item.Value);
                if (code == value) return item.DisplayText;
            }
            return "";
        }
        /// <summary>
        /// Virtuální metoda převodu hodnoty na text, konkrétně řeší formátování podle soupisu položek, 
        /// když hodnota je desetinného typu.
        /// </summary>
        /// <param name="value">Hodnota</param>
        /// <param name="items">Položky</param>
        /// <returns></returns>
        private string ConvertDecimalCodeToTextByItems(decimal value, IEnumerable<FormatStyleItem> items)
        {
            foreach (FormatStyleItem item in items)
            {
                decimal code = Convert.ToDecimal(item.Value);
                if (code == value) return item.DisplayText;
            }
            return "";
        }
        /// <summary>
        /// Virtuální metoda převodu hodnoty na text, konkrétně řeší formátování podle soupisu položek, 
        /// když hodnota je řetězcového typu.
        /// </summary>
        /// <param name="value">Hodnota</param>
        /// <param name="items">Položky</param>
        /// <returns></returns>
        private string ConvertStringCodeToTextByItems(string value, IEnumerable<FormatStyleItem> items)
        {
            foreach (FormatStyleItem item in items)
            {
                string code = Convert.ToString(item.Value);
                if (code == value) return item.DisplayText;
            }
            return "";
        }
        #endregion
        #region ... formátování podle masky
        /// <summary>
        /// Vrátí masku pro formátování daného datového typu.
        /// Volá se tehdy, když nejsou definované položky editačního stylu.
        /// Implicitně vrátí this.FormatMask.
        /// Může vrátit prázdný string, pak se bere jako nespecifická maska a použije se formátování implicitní.
        /// Může vrátit null, pak se hodnota zformátuje systémovým ToString().
        /// </summary>
        /// <param name="value">Data</param>
        /// <param name="valueType">Typ dat</param>
        /// <returns></returns>
        protected virtual string GetMaskForType(ValueData value)
        {
            if (!String.IsNullOrEmpty(this.FormatMask))
                return this.FormatMask;

            switch (value.Type)
            {
                case ValueDataType.Boolean:
                    return "";
                case ValueDataType.Integer:
                    return "### ### ### ##0";
                case ValueDataType.Decimal:
                    return "### ### ### ##0.00";
                case ValueDataType.Character:
                    return "";
                case ValueDataType.DateTime:
                    DateTime dateTime = (DateTime)value;
                    if (dateTime.TimeOfDay.Ticks == 0L)
                        return "d.M.yyyy";
                    return "d.M.yyyy HH:mm";
                case ValueDataType.TimeSpan:
                    TimeSpan timeSpan = (TimeSpan)value;
                    if (timeSpan.Days == 0)
                        return "hh':'mm':'ss";
                    return "d'; 'hh':'mm':'ss";
            }
            return "";
        }
        /// <summary>
        /// Virtuální metoda převodu hodnoty na text, konkrétně řeší formátování podle masky.
        /// Na vstupu není NULL, a případné Nullable typy jsou ošetřené (pokud byly Null, nejsou zde, 
        /// a pokud měly hodnotu pak na vstupu této metody je jejich vnitřní hodnota, například místo Int32? je na vstupu Int32?.Value.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="mask"></param>
        /// <returns></returns>
        protected virtual string ConvertCodeToTextByMask(ValueData value, string mask)
        {
            switch (value.Type)
            {
                case ValueDataType.Boolean:
                    return this.ConvertBooleanValueToTextByMask(value.ValueBoolean, mask);
                case ValueDataType.Integer:
                    return this.ConvertInt64ValueToTextByMask(value.ValueInteger, mask);
                case ValueDataType.Decimal:
                    return this.ConvertDecimalValueToTextByMask(value.ValueDecimal, mask);
                case ValueDataType.Character:
                    return this.ConvertStringValueToTextByMask(value.ValueCharacter, mask);
                case ValueDataType.DateTime:
                    return this.ConvertDateTimeValueToTextByMask(value.ValueDateTime, mask);
                case ValueDataType.TimeSpan:
                    return this.ConvertTimeSpanValueToTextByMask(value.ValueTimeSpan, mask);
            }
            return this.ConvertOtherValueToTextByMask(value, mask);
        }

        protected virtual string ConvertBooleanValueToTextByMask(Boolean value, string mask)
        {
            return (value ? "A" : "N");
        }

        protected virtual string ConvertInt64ValueToTextByMask(Int64 value, string mask)
        {
            return value.ToString(mask);
        }

        protected virtual string ConvertDecimalValueToTextByMask(Decimal value, string mask)
        {
            return value.ToString(mask);
        }

        protected virtual string ConvertStringValueToTextByMask(String value, string mask)
        {
            return value;
        }

        protected virtual string ConvertDateTimeValueToTextByMask(DateTime value, string mask)
        {
            return value.ToString(mask);
        }

        protected virtual string ConvertTimeSpanValueToTextByMask(TimeSpan value, string mask)
        {
            return value.ToString(mask);
        }

        protected virtual string ConvertOtherValueToTextByMask(object value, string mask)
        {
            return value.ToString();
        }
        #endregion
        #endregion
        #region Konverze textu (obecně hodnoty zadané uživatelem) na hodnotu do sloupce
        /// <summary>
        /// Převede hodnotu zadanou uživatelem na datový typ a hodnotu do tabulky (=do databáze).
        /// </summary>
        /// <param name="text"></param>
        /// <param name="columnDataType"></param>
        /// <returns></returns>
        public virtual ValueData ConvertTextToValue(object text, ValueDataType columnDataType)
        {
            return new ValueData(text);
        }
        #endregion
        #region Podpora serializací
        /// <summary>
        /// Naplní svoje proměnné z dodané definice.
        /// Definice může mít tři tvary (nepočítaje v to null / empty)
        /// a) text = maska;
        /// b) text v lomených závorkách obsahující {Namespace.Name} = název typu enum, jehož položky se mají nabízet
        /// c) text obsahující znaky : a «» = tabulka typu: hodnota1:«text1»hodnota2:«text2»
        /// </summary>
        /// <param name="definition"></param>
        /// <returns></returns>
        public static FormatStyle CreateFromDefinition(string definition)
        {
            if (String.IsNullOrEmpty(definition)) return null;

            definition = definition.Trim();
            FormatStyle style = new FormatStyle();
            if (definition.StartsWith("{") && definition.EndsWith("}"))
                style.FillForType(definition.Substring(1, definition.Length - 2));
            else if (definition.Contains("«") && definition.Contains("»") && definition.Contains(":"))
                style.FillForList(definition);
            else
                style.FillForMask(definition);

            return style;
        }
        /// <summary>
        /// Naplní položky editačního stylu pro Type daný jeho názvem.
        /// Název typu musí být FullName = Namespace.AllName, což u tříd definovaných vnořeně v jiné třídě vypadá například:
        /// "Asol.Reporting.Test.Form+TestClass" (právě to "+" uprostřed).
        /// </summary>
        /// <param name="typeName"></param>
        private void FillForType(string typeName)
        {
            this.EditStyleItems = new List<FormatStyleItem>();
            System.Type type;
            if (!Assistant.TryGetType(typeName.Trim(), out type)) return;
            this.EditStyleItems = Assistant.GetEditStyleItemsFromType(type);
        }
        /// <summary>
        /// Naplní položky editačního stylu z dodaného seznamu.
        /// Seznam má tvar: hodnota1:«text1»hodnota2:«text2»
        /// Tedy za hodnotu je dvojtečka, a okolo textu jsou znaky « a ».
        /// </summary>
        /// <param name="definition"></param>
        private void FillForList(string definition)
        {
            this.EditStyleItems = new List<FormatStyleItem>();
            List<FormatStyleItem> itemList = new List<FormatStyleItem>();
            string[] items = definition.Split('»');
            foreach (string item in items)
            {
                int at = item.IndexOf(':');
                if (at <= 0 || at >= item.Length) continue;
                string value = item.Substring(0, at);
                string text = item.Substring(at + 1);
                itemList.Add(new FormatStyleItem(value, text));
            }
            this.EditStyleItems = itemList;
        }
        /// <summary>
        /// Dodanou masku vloží do this, zruší seznam položek.
        /// </summary>
        /// <param name="mask"></param>
        private void FillForMask(string mask)
        {
            this.FormatMask = mask;
            this.EditStyleItems = null;
        }
        #endregion
    }
    #endregion
    #region class FormatStyleItem : Jedna položka editačního stylu (hodnota = text {tooltip info})
    /// <summary>
    /// Jedna položka editačního stylu (hodnota = text {tooltip info})
    /// </summary>
    public class FormatStyleItem
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="value"></param>
        /// <param name="text"></param>
        public FormatStyleItem(object value, string text)
        {
            this.Value = value;
            this.Text = text;
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="value"></param>
        /// <param name="text"></param>
        /// <param name="info"></param>
        public FormatStyleItem(object value, string text, string info)
        {
            this.Value = value;
            this.Text = text;
            this.Info = info;
        }
        public override string ToString()
        {
            return this.DisplayText;
        }
        /// <summary>
        /// Datová hodnota
        /// </summary>
        public object Value { get; private set; }
        /// <summary>
        /// Zobrazovaný text.
        /// Respektive kód hlášky, aktuální překlad bude získán přes Message.
        /// </summary>
        public string Text { get; private set; }
        /// <summary>
        /// Rozšiřující informace (tooltip).
        /// Respektive kód hlášky, aktuální překlad bude získán přes Message.
        /// </summary>
        public string Info { get; private set; }
        /// <summary>
        /// Zobrazovaný text, již převedený přes Assistant.GetMessage(this.Text).
        /// </summary>
        public string DisplayText { get { return Assistant.GetMessage(this.Text); } }
        /// <summary>
        /// Rozšiřující informace (tooltip), již převedený přes Assistant.GetMessage(this.Info).
        /// </summary>
        public string DisplayInfo { get { return Assistant.GetMessage(this.Info); } }
    }
    #endregion
}
